package com.nulldev.util.languages.json.uniapi;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.nio.channels.WritableByteChannel;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

import com.nulldev.util.JVM.JVM;
import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.VariableAPI.StringsUtil;
import com.nulldev.util.VariableAPI.util.strings.splitters.third_party.Splitter;
import com.nulldev.util.data.Variables;
import com.nulldev.util.data.variable;
import com.nulldev.util.data.Arrays.arrays.FastLists;
import com.nulldev.util.data.Arrays.maps.FastMaps;
import com.nulldev.util.internal.backport.concurrency9.Lists;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.io.NIOUtils;
import com.nulldev.util.io.extensions.copyable.Copyable;
import com.nulldev.util.io.extensions.copyable.nio.NIOCopyable;
import com.nulldev.util.languages.json.lazy.LazyArray;
import com.nulldev.util.languages.json.lazy.LazyObject;
import com.nulldev.util.languages.json.lazy.LazyType;
import com.nulldev.util.languages.json.v2.JsonArray;
import com.nulldev.util.languages.json.v2.JsonParser;
import com.nulldev.util.languages.json.v2.JsonParserException;
import com.nulldev.util.languages.json.v2.JsonWriter;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.manage.Version;

public class UniJsonValue implements Copyable, NIOCopyable {

	/**
	 * UniJSON API
	 * 
	 * A wrapper over many JSON interfaces, since I couldn't choose one.
	 * 
	 * Changelog: <br>
	 * 1.0.4 <br>
	 * - Major API change for Streams. <br>
	 * - [Streams] Support for findAny(), findFirst() and filter(). <br>
	 * 1.0.3 <br>
	 * - Remove asObject(). <br>
	 * - Add stream(), parallelStream() and stream(boolean). <br>
	 * - Begin adding comments for functions. <br>
	 * 1.0.2 <br>
	 * - [v2] Disable indenting when using writeTo(). <br>
	 * 1.0.1 <br>
	 * - API improvements <br>
	 * - Remove JSON v1. <br>
	 * 1.0.0 <br>
	 * - Initial release
	 */

	public static final Version version = new Version("1.0.4");
	private static final Logger log = LoggerFactory.getLogger();
	protected final Object o;

	public UniJsonValue(final Object o) {
		if (o == null)
			throw new NullPointerException("o is null!");
		if (!(o instanceof UniJsonValue || o instanceof com.nulldev.util.languages.json.v2.JsonObject
				|| o instanceof com.nulldev.util.languages.json.v2.JsonArray || o instanceof LazyObject || o instanceof LazyArray || o instanceof String
				|| o instanceof Number || o instanceof Boolean))
			throw new NullPointerException("o is not a valid object! [o: " + o + "]");
		if (o instanceof UniJsonValue) {
			this.o = ((UniJsonValue) o).o;
		} else {
			this.o = o;
		}
	}

	public UniJsonValue unsafeGet(final String item) {
		nullCheck();
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			return new UniJsonValue(((com.nulldev.util.languages.json.v2.JsonObject) o).get(item));
		} else if (o instanceof LazyObject) {
			return new UniJsonValue(((LazyObject) o).get(item));
		} else {
			throw new UnsupportedOperationException("unsafeGet(String) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	/**
	 * Attempts to get a value within the list of items provided, or returns null.
	 * 
	 * @param items
	 * @returns UniJsonValue or null
	 */
	public UniJsonValue getEither(final String... items) {
		for (final String item : items)
			if (this.contains(item))
				return this.get(item);
		return null;
	}

	public UniJsonValue get(final String item) {
		nullCheck(item);
		if (o == null)
			return null;
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			try {
				final Object _o = ((com.nulldev.util.languages.json.v2.JsonObject) o).get(item);
				if (_o == null)
					return null;
				return new UniJsonValue(_o);
			} catch (Exception e) {
				return null;
			}
		} else if (o instanceof LazyObject) {
			try {
				final Object _o = ((LazyObject) o).get(item);
				if (_o == null)
					return null;
				return new UniJsonValue(_o);
			} catch (Exception e) {
				return null;
			}
		} else {
			throw new UnsupportedOperationException("get(String) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public UniJsonValue getIgnoreNull(final String item) {
		if (isNull())
			return null;
		return this.get(item);
	}

	/**
	 * Attempts to get an object, if it is null or not found, will return a blank
	 * object instead.
	 * 
	 * @param item
	 * @returns UniJsonValue
	 */
	public UniJsonValue safe(final String item) {
		try {
			final UniJsonValue obj = this.get(item);
			if (obj == null || obj.isNull()) {
				log.warn("safe(): Failed to get value '" + item + "'.");
				return object();
			}
			return obj;
		} catch (Exception e) {
			return object();
		}
	}

	public UniJsonValue get(final int index) {
		if (index < 0)
			throw new IllegalArgumentException("invalid index!");
		nullCheck();
		// XXX: Shouldn't there be an arrayCheck() here???
		if (o instanceof com.nulldev.util.languages.json.v2.JsonArray) {
			return new UniJsonValue(((com.nulldev.util.languages.json.v2.JsonArray) o).get(index));
		} else if (o instanceof LazyArray) {
			return new UniJsonValue(((LazyArray) o).get(index));
		} else {
			throw new UnsupportedOperationException("get(int) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	protected UniJsonValue get_array() {
		nullCheck();
		// XXX: Shouldn't there be an arrayCheck() here???
		if (o instanceof com.nulldev.util.languages.json.v2.JsonArray) {
			return new UniJsonValue(((com.nulldev.util.languages.json.v2.JsonArray) this.o)); // .toV1Array()
		} else if (o instanceof LazyArray) {
			return new UniJsonValue(((LazyArray) this.o));
		} else {
			throw new UnsupportedOperationException("get_array() -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public String string(final String item) {
		nullCheck();
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			return ((com.nulldev.util.languages.json.v2.JsonObject) o).getString(item);
		} else if (o instanceof LazyObject) {
			return ((LazyObject) o).getString(item);
		} else {
			throw new UnsupportedOperationException("string(String) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public String strings(final String... items) {
		nullCheck();
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			for (final String item : items) {
				if (((com.nulldev.util.languages.json.v2.JsonObject) o).containsKey(item))
					return ((com.nulldev.util.languages.json.v2.JsonObject) o).getString(item);
			}
			return null;
		} else if (o instanceof LazyObject) {
			for (final String item : items) {
				if (((LazyObject) o).has(item))
					return ((LazyObject) o).getString(item);
			}
			return null;
		} else {
			throw new UnsupportedOperationException("strings(String...) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public int integer(final String item) {
		nullCheck();
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			final com.nulldev.util.languages.json.v2.JsonObject _o = ((com.nulldev.util.languages.json.v2.JsonObject) o);
			if (_o.isNumber(item)) {
				return (int) _o.getNumber(item);
			} else {
				return Integer.parseInt(_o.getString(item));
			}
		} else if (o instanceof LazyObject) {
			return ((LazyObject) o).getInt(item);
		} else {
			throw new UnsupportedOperationException("integer(String) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public int integer(final String item, final int fallback) {
		if (this.o == null)
			return fallback;
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			final com.nulldev.util.languages.json.v2.JsonObject _o = ((com.nulldev.util.languages.json.v2.JsonObject) o);
			if (_o.isNumber(item)) {
				return (int) _o.getNumber(item, fallback);
			} else {
				return Integer.parseInt(_o.getString(item, String.valueOf(fallback)));
			}
		} else if (o instanceof LazyObject) {
			try {
				return ((LazyObject) o).getInt(item);
			} catch (Exception e) {
				return fallback;
			}
		} else {
			throw new UnsupportedOperationException("integer(String) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public double doublev(final String item) {
		nullCheck();
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			final com.nulldev.util.languages.json.v2.JsonObject _o = ((com.nulldev.util.languages.json.v2.JsonObject) o);
			if (_o.isNumber(item)) {
				return (double) _o.getNumber(item);
			} else {
				return Double.parseDouble(_o.getString(item));
			}
		} else if (o instanceof LazyObject) {
			try {
				return ((LazyObject) o).getDouble(item);
			} catch (Exception e) {
				return -1;
			}
		} else {
			throw new UnsupportedOperationException("doublev(String) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public double doublev(final String item, final double fallback) {
		if (this.o == null)
			return fallback;
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			final com.nulldev.util.languages.json.v2.JsonObject _o = ((com.nulldev.util.languages.json.v2.JsonObject) o);
			if (_o.isNumber(item)) {
				return (double) _o.getNumber(item, fallback);
			} else {
				return Double.parseDouble(_o.getString(item, String.valueOf(fallback)));
			}
		} else if (o instanceof LazyObject) {
			try {
				return ((LazyObject) o).getDouble(item);
			} catch (Exception e) {
				return fallback;
			}
		} else {
			throw new UnsupportedOperationException("doublev(String) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public long longv(final String item) {
		nullCheck();
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			final com.nulldev.util.languages.json.v2.JsonObject _o = ((com.nulldev.util.languages.json.v2.JsonObject) o);
			if (!_o.containsKey(item)) {
				throw new NullPointerException("Cannot find child: " + item);
			} else if (_o.isNumber(item)) {
				final Number x = _o.getNumber(item);
				if (x instanceof Integer)
					return (int) x;
				return (long) x;
			} else if (_o.get(item) instanceof Long) {
				return (Long) _o.get(item);
			} else if (_o.get(item) instanceof Integer) {
				return (long) (Integer) _o.get(item);
			} else {
				return Long.parseLong(_o.getString(item));
			}
		} else if (o instanceof LazyObject) {
			try {
				return ((LazyObject) o).getLong(item);
			} catch (Exception e) {
				return -1;
			}
		} else {
			throw new UnsupportedOperationException("longv(String) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public long longv(final String item, final long fallback) {
		if (this.o == null)
			return fallback;
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			final com.nulldev.util.languages.json.v2.JsonObject _o = ((com.nulldev.util.languages.json.v2.JsonObject) o);
			if (_o.isNumber(item)) {
				final Number x = _o.getNumber(item, fallback);
				if (x instanceof Integer)
					return (int) x;
				return (long) x;
			} else {
				return Long.parseLong(_o.getString(item, String.valueOf(fallback)));
			}
		} else if (o instanceof LazyObject) {
			try {
				return ((LazyObject) o).getLong(item);
			} catch (Exception e) {
				return fallback;
			}
		} else {
			throw new UnsupportedOperationException("longv(String) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public boolean bool(final String item) {
		nullCheck();
		try {
			if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
				return ((com.nulldev.util.languages.json.v2.JsonObject) o).getBoolean(item);
			} else if (o instanceof LazyObject) {
				return ((LazyObject) o).getBoolean(item);
			} else {
				throw new UnsupportedOperationException("bool(String) -> Unsupported operation on " + o.getClass().getName() + "!");
			}
		} catch (Exception e) {
			return false;
		}
	}

	public boolean bool(final String item, final boolean fallback) {
		if (o == null)
			return fallback;
		try {
			if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
				return ((com.nulldev.util.languages.json.v2.JsonObject) o).getBoolean(item, fallback);
			} else if (o instanceof LazyObject) {
				if (!((LazyObject) o).has(item))
					return fallback;
				return ((LazyObject) o).getBoolean(item);
			} else {
				return fallback;
			}
		} catch (Exception e) {
			return fallback;
		}
	}

	public boolean getBoolean(final String item) {
		return this.bool(item);
	}

	public String getString(final String item) {
		return this.string(item);
	}

	public String getStrings(final String... items) {
		return this.strings(items);
	}

	public String getString(final String item, final String fallback) {
		try {
			final String _i = this.string(item);
			if (_i == null) {
				return fallback;
			}
			return _i;
		} catch (Exception e) {
			return fallback;
		}
	}

	public int getInt(final String item) {
		return this.integer(item);
	}

	public int getInt(final String item, final int fallback) {
		try {
			return this.integer(item, fallback);
		} catch (Exception e) {
			return fallback;
		}
	}

	public double getDouble(final String item) {
		return this.doublev(item);
	}

	public double getDouble(final String item, final double fallback) {
		try {
			return this.doublev(item);
		} catch (Exception e) {
			return fallback;
		}
	}

	public long getLong(final String item) {
		return this.longv(item);
	}

	public long getLong(final String item, final long fallback) {
		try {
			return this.longv(item);
		} catch (Exception e) {
			return fallback;
		}
	}

	public UniJsonValue getJSONArray(final String item) {
		nullCheck();
		final UniJsonValue i = this.get(item);
		i.arrayCheck();
		return i;
	}

	public UniJsonValue getJSONObject(final String item) {
		nullCheck();
		objectCheck();
		return this.get(item);
	}

	private void nullCheck() {
		if (this.o == null) {
			throw new NullPointerException("object is null!");
		}
	}

	private void nullCheck(final String item) {
		if (this.o == null) {
			throw new NullPointerException("object is null! [ref: " + item + "]");
		}
	}

	public UniJsonValue getJsonObject(final int index) {
		if (index < 0)
			throw new UnsupportedOperationException("Illegal index: " + index);
		return this.get(index);
	}

	public UniJsonIterableValue asArray() {
		arrayCheck();
		return new UniJsonIterableValue(this);
	}

	public int length() {
		if (this.isNull()) {
			return 0;
		} else if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			return ((com.nulldev.util.languages.json.v2.JsonObject) o).size();
		} else if (o instanceof com.nulldev.util.languages.json.v2.JsonArray) {
			return ((com.nulldev.util.languages.json.v2.JsonArray) o).size();
		} else if (o instanceof LazyObject) {
			return ((LazyObject) o).length();
		} else if (o instanceof LazyArray) {
			return ((LazyArray) o).length();
		} else {
			String o1 = String.valueOf(o);
			try {
				o1 = o.getClass().getName();
			} catch (Exception e) {
			}
			throw new UnsupportedOperationException("length() -> Unsupported operation on " + o1 + "!");
		}
	}

	public boolean contains(final String... items) {
		if (this.o == null)
			return false;
		if (items.length == 0)
			return false;
		for (final String item : items) {
			if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
				if (((com.nulldev.util.languages.json.v2.JsonObject) o).has(item))
					return true;
			} else if (o instanceof LazyObject) {
				if (((LazyObject) o).has(item))
					return true;
			} else if (o instanceof com.nulldev.util.languages.json.v2.JsonArray) {
				if (((com.nulldev.util.languages.json.v2.JsonArray) o).contains(item))
					return true;
			} else if (o instanceof LazyArray) {
				if (((LazyArray) o).contains(item))
					return true;
			} else {
				throw new UnsupportedOperationException("contains(String) -> Unsupported operation on " + o.getClass().getName() + "!");
			}
		}
		return false;
	}

	public static UniJsonValue parse(final String string) {
		return parse(string, false);
	}

	public static UniJsonValue parse(final String string, final boolean lazy) {
		Variables.notNullE(string);
		if (string.isEmpty())
			throw new IllegalArgumentException("string is empty!");

		if (api.equals(JSONAPI.V2)) {
			if (string.startsWith("[") && string.endsWith("]")) {
				try {
					return new UniJsonValue(JsonParser.array().from(string, lazy));
				} catch (JsonParserException e) {
					throw new RuntimeException(e);
				}
			} else {
				try {
					return new UniJsonValue(JsonParser.object().from(string, lazy));
				} catch (JsonParserException e) {
					throw new RuntimeException(e);
				}
			}
		} else if (api.equals(JSONAPI.FAST)) {
			if (string.startsWith("[") && string.endsWith("]")) {
				return new UniJsonValue(new LazyArray(string));
			} else {
				return new UniJsonValue(new LazyObject(string));
			}
		} else {
			throw new UnsupportedOperationException("Unknown API: " + api);
		}
	}

	public static UniJsonValue parse(final InputStream is) {
		return parse(is, false);
	}

	public static UniJsonValue parse(final InputStream is, final boolean lazy) {
		Variables.notNullE(is);
		if (api.equals(JSONAPI.V2)) {
			try {
				return new UniJsonValue(JsonParser.object().from(is, lazy));
			} catch (JsonParserException e) {
				throw new RuntimeException(e);
			}
		} else if (api.equals(JSONAPI.FAST)) {
			String string;
			try {
				string = new String(IOUtils.toByteArray(is));
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
			if (string.startsWith("[") && string.endsWith("]")) {
				return new UniJsonValue(new LazyArray(string));
			} else {
				return new UniJsonValue(new LazyObject(string));
			}
		} else {
			throw new UnsupportedOperationException("Unknown API: " + api);
		}
	}

	public static UniJsonValue parse(final Reader r) {
		return parse(r, false);
	}

	public static UniJsonValue parse(final Reader r, final boolean lazy) {
		Variables.notNullE(r);
		if (api.equals(JSONAPI.V2)) {
			try {
				return new UniJsonValue(JsonParser.object().from(r, lazy));
			} catch (JsonParserException e) {
				throw new RuntimeException(e);
			}
		} else if (api.equals(JSONAPI.FAST)) {
			String string;
			try {
				string = IOUtils.readerToString(r);
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
			if (string.startsWith("[") && string.endsWith("]")) {
				return new UniJsonValue(new LazyArray(string));
			} else {
				return new UniJsonValue(new LazyObject(string));
			}
		} else {
			throw new UnsupportedOperationException("Unknown API: " + api);
		}
	}

	public static UniJsonValue parseArray(final String string) {
		Variables.notNullE(string);
		if (string.isEmpty())
			throw new IllegalArgumentException("string is empty!");
		if (api.equals(JSONAPI.V2)) {
			try {
				return new UniJsonValue(JsonParser.array().from(string));
			} catch (Exception e) {
				log.error("Failed to parse!", e);
				return null;
			}
		} else {
			return new UniJsonValue(new LazyArray(string));
		}
	}

	public static UniJsonValue parseArray(final String string, final boolean lazy) {
		Variables.notNullE(string);
		if (string.isEmpty())
			throw new IllegalArgumentException("string is empty!");
		if (api.equals(JSONAPI.V2)) {
			try {
				return new UniJsonValue(JsonParser.array().from(string, lazy));
			} catch (JsonParserException e) {
				throw new RuntimeException(e);
			}
		} else {
			return new UniJsonValue(new LazyArray(string));
		}
	}

	public static UniJsonValue parseArray(final InputStream is) {
		Variables.notNullE(is);
		if (api.equals(JSONAPI.V2)) {
			try {
				return new UniJsonValue(JsonParser.array().from(is));
			} catch (JsonParserException e) {
				throw new RuntimeException(e);
			}
		} else {
			try {
				return new UniJsonValue(new LazyArray(new String(IOUtils.toByteArray(is))));
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
	}

	public static final JSONAPI api = JSONAPI.get();

	public UniJsonValue getSafe(final String string) {
		try {
			final UniJsonValue g = this.get(string);
			if (g == null || g.isNull()) {
				return object();
			}
			return g;
		} catch (Exception e) {
			return object();
		}
	}

	public String[] toArray() {
		try {
			final UniJsonValue array = this.asArray();
			String[] objs = new String[array.length()];
			for (int i = 0; i < array.length(); i++) {
				objs[i] = array.get(i).toString();
			}
			return objs;
		} catch (Exception e) {
			return new String[0];
		}
	}

	public boolean hasValue(final String string) {
		return this.contains(string);
	}

	@Override
	public String toString() {
		if (this.isNull()) {
			return "null";
		} else if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			return JsonWriter.string(((com.nulldev.util.languages.json.v2.JsonObject) o));
		} else if (o instanceof String) {
			return (String) this.o;
		} else if (o instanceof com.nulldev.util.languages.json.v2.JsonArray) {
			// return ((com.nulldev.util.languages.json.v2.JsonArray)o).toString();
			return JsonWriter.string(((com.nulldev.util.languages.json.v2.JsonArray) o));
		} else if (o instanceof LazyObject) {
			return ((LazyObject) o).toString();
		} else if (o instanceof LazyArray) {
			return ((LazyArray) o).toString();
		} else if (o instanceof Object) {
			return o == null ? String.valueOf(o) : o.toString();
		} else {
			throw new UnsupportedOperationException("toString() -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public boolean isArray() {
		return this.o != null && (this.o instanceof com.nulldev.util.languages.json.v2.JsonArray || this.o instanceof LazyArray);
	}

	public void clear() {
		if (this.o != null && this.o instanceof com.nulldev.util.languages.json.v2.JsonArray) {
			((com.nulldev.util.languages.json.v2.JsonArray) this.o).clear();
		} else if (this.o != null && this.o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			((com.nulldev.util.languages.json.v2.JsonObject) this.o).clear();
		}
	}

	/* NCOMPAT: finalize() was removed as JRE 18 removes finalization. */

	public UniJsonValue arrayGet(final String item) {
		try {
			return this.get(item).asArray();
		} catch (Exception e) {
			return new UniJsonValue(new JsonArray());
		}
	}

	public int size() {
		return this.length();
	}

	public void println() {
		JVM.println(this.toString());
	}

	public static UniJsonValue emptyObject() {
		return object(0);
	}

	public static UniJsonValue object() {
		if (api.equals(JSONAPI.V2)) {
			return new UniJsonValue(new com.nulldev.util.languages.json.v2.JsonObject());
		} else {
			return new UniJsonValue(new LazyObject());
		}
	}

	public static UniJsonValue object(final int size) {
		if (size < 0)
			throw new IllegalArgumentException("Invalid size: " + size);
		if (api.equals(JSONAPI.V2)) {
			return new UniJsonValue(new com.nulldev.util.languages.json.v2.JsonObject(size));
		} else {
			return new UniJsonValue(new LazyObject());
		}
	}

	public static UniJsonValue array() {
		if (api.equals(JSONAPI.V2)) {
			return new UniJsonValue(new com.nulldev.util.languages.json.v2.JsonArray());
		} else {
			return new UniJsonValue(new LazyArray());
		}
	}

	public static UniJsonValue emptyArray() {
		return array(0);
	}

	public static UniJsonValue array(final int size) {
		if (size < 0)
			throw new IllegalArgumentException("Invalid size: " + size);
		if (api.equals(JSONAPI.V2)) {
			return new UniJsonValue(new com.nulldev.util.languages.json.v2.JsonArray(size));
		} else {
			return new UniJsonValue(new LazyArray());
		}
	}

	public static UniJsonValue array(final List<Object> objects) {
		if (Variables.requireNonNullObject(objects, "objects").size() <= 0)
			throw new IllegalArgumentException("Invalid size: " + objects.size());
		if (api.equals(JSONAPI.V2)) {
			return new UniJsonValue(new com.nulldev.util.languages.json.v2.JsonArray(objects));
		} else {
			final LazyArray lza = new LazyArray();
			for (final Object o : objects)
				lza.put(o);
			return new UniJsonValue(lza);
		}
	}

	public static UniJsonValue array(final Object... objects) {
		if (objects.length <= 0)
			throw new IllegalArgumentException("Invalid size: " + objects.length);
		if (api.equals(JSONAPI.V2)) {
			return new UniJsonValue(new com.nulldev.util.languages.json.v2.JsonArray(Lists.of(objects)));
		} else {
			final LazyArray lza = new LazyArray();
			for (final Object o : objects)
				lza.put(o);
			return new UniJsonValue(lza);
		}
	}

	public UniJsonValue add(final String key, final Object value) {
		if (value == null) {
			this.add(key, (String) null);
		} else if (value instanceof Integer) {
			this.add(key, (int) value);
		} else if (value instanceof Long) {
			this.add(key, (long) value);
		} else if (value instanceof Float) {
			this.add(key, (float) value);
		} else if (value instanceof Double) {
			this.add(key, (double) value);
		} else if (value instanceof Boolean) {
			this.add(key, (boolean) value);
		} else if (value instanceof UniJsonValue) {
			this.add(key, (UniJsonValue) value);
		} else if (value instanceof Object[]) {
			final UniJsonValue arr = UniJsonValue.array();
			arr.addAll((Object[]) value);
			this.add(key, arr);
		} else {
			this.add(key, String.valueOf(value));
		}
		return this;
	}

	public UniJsonValue add(final String key, final String value) {
		if (key == null || key.isEmpty())
			throw new IllegalArgumentException("key is null or empty!");
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			((com.nulldev.util.languages.json.v2.JsonObject) o).put(key, value);
		} else if (o instanceof LazyObject) {
			((LazyObject) o).put(key, value);
		} else {
			throw new UnsupportedOperationException("add(String, String) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
		return this;
	}

	public void add(final String key) {
		if (key == null)
			throw new NullPointerException("key is null!");
		if (!this.isArray())
			throw new UnsupportedOperationException("o is not an array!");
		if (o instanceof JsonArray) {
			((JsonArray) o).add(key);
		} else if (o instanceof com.nulldev.util.languages.json.v2.JsonArray) {
			((com.nulldev.util.languages.json.v2.JsonArray) o).add(key);
		} else if (o instanceof LazyArray) {
			((LazyArray) o).put(key);
		} else {
			throw new UnsupportedOperationException("add(Object) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public void add(final Object key) {
		if (key == null)
			throw new NullPointerException("key is null!");
		if (!this.isArray())
			throw new UnsupportedOperationException("o is not an array!");
		if (o instanceof JsonArray) {
			((JsonArray) o).add(key);
		} else if (o instanceof com.nulldev.util.languages.json.v2.JsonArray) {
			((com.nulldev.util.languages.json.v2.JsonArray) o).add(key);
		} else if (o instanceof LazyArray) {
			try {
				((LazyArray) o).put(key);
			} catch (Exception e) {
				((LazyArray) o).put(String.valueOf(key));
			}
		} else {
			throw new UnsupportedOperationException("add(Object) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public void add(final String key, final float value) {
		if (key == null || key.isEmpty())
			throw new IllegalArgumentException("key is null or empty!");
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			((com.nulldev.util.languages.json.v2.JsonObject) o).put(key, value);
		} else if (o instanceof LazyObject) {
			((LazyObject) o).put(key, value);
		} else {
			throw new UnsupportedOperationException("add(String, double) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public void add(final String key, final double value) {
		if (key == null || key.isEmpty())
			throw new IllegalArgumentException("key is null or empty!");
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			((com.nulldev.util.languages.json.v2.JsonObject) o).put(key, value);
		} else if (o instanceof LazyObject) {
			((LazyObject) o).put(key, value);
		} else {
			throw new UnsupportedOperationException("add(String, double) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public void add(final String key, final int value) {
		if (key == null || key.isEmpty())
			throw new IllegalArgumentException("key is null or empty!");
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			((com.nulldev.util.languages.json.v2.JsonObject) o).put(key, value);
		} else if (o instanceof LazyObject) {
			((LazyObject) o).put(key, value);
		} else {
			throw new UnsupportedOperationException("add(String, int) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public UniJsonValue add(final String key, final long value) {
		if (key == null || key.isEmpty())
			throw new IllegalArgumentException("key is null or empty!");
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			((com.nulldev.util.languages.json.v2.JsonObject) o).put(key, value);
		} else if (o instanceof LazyObject) {
			((LazyObject) o).put(key, value);
		} else {
			throw new UnsupportedOperationException("add(String, long) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
		return this;
	}

	public UniJsonValue add(final String key, final boolean value) {
		if (key == null || key.isEmpty())
			throw new IllegalArgumentException("key is null or empty!");
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			((com.nulldev.util.languages.json.v2.JsonObject) o).put(key, value);
		} else if (o instanceof LazyObject) {
			((LazyObject) o).put(key, value);
		} else {
			throw new UnsupportedOperationException("add(String, boolean) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
		return this;
	}

	public UniJsonValue add(final String key, final UniJsonValue value) {
		Variables.notNullE(key);
		if (key.isEmpty())
			throw new UnsupportedOperationException("add: Cannot add a blank key!");
		Variables.notNullE(value);
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			try {
				if (value.o instanceof com.nulldev.util.languages.json.v2.JsonArray) {
					log.debug("add: key: " + key + ", value: " + value.o);
					((com.nulldev.util.languages.json.v2.JsonObject) o).put(key, JsonParser.array().from(value.toString()));
				} else if (value.o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
					log.debug("add: key: " + key + ", value: " + value.o);
					((com.nulldev.util.languages.json.v2.JsonObject) o).put(key, JsonParser.object().from(value.toString()));
				} else {
					log.debug("add: key: " + key + ", value: " + value);
					((com.nulldev.util.languages.json.v2.JsonObject) o).put(key, value.o);
				}
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		} else if (o instanceof LazyObject) {
			((LazyObject) o).put(key, value.o);
		} else {
			throw new UnsupportedOperationException("add(String, UniJsonValue) -> Unsupported operation on " + o.getClass().getName() + "!");
		}

		return this;
	}

	public void add(final String key, final JsonArray value) {
		if (key == null || key.isEmpty())
			throw new IllegalArgumentException("key is null or empty!");
		Variables.notNullE(value);
		if (o instanceof com.nulldev.util.languages.json.v2.JsonArray) {
			try {
				((com.nulldev.util.languages.json.v2.JsonObject) o).put(key, JsonParser.array().from(value.toString()));
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		} else {
			throw new UnsupportedOperationException("add(String, JsonArray) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public void add(final UniJsonValue v) {
		arrayCheck();
		if (v == null)
			throw new NullPointerException("v is null!");
		if (o instanceof JsonArray) {
			((JsonArray) o).add(v.o);
		} else if (o instanceof com.nulldev.util.languages.json.v2.JsonArray) {
			((com.nulldev.util.languages.json.v2.JsonArray) o).add(v.o);
		} else if (o instanceof LazyArray) {
			((LazyArray) o).put(v.o);
		} else {
			throw new UnsupportedOperationException("add(UniJsonValue) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public boolean isEmpty() {
		try {
			return this.size() == 0;
		} catch (Exception e) {
			return true;
		}
	}

	public UniJsonValue last() {
		arrayCheck();
		if (this.isEmpty())
			throw new UnsupportedOperationException("Array is empty!");
		return this.get(this.size() - 1);
	}

	public UniJsonValue first() {
		arrayCheck();
		if (this.isEmpty())
			throw new UnsupportedOperationException("Array is empty!");
		return this.get(0);
	}

	public boolean isNull() {
		return this.o == null;
	}

	public float getFloat(final String item) {
		nullCheck();
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			return (float) ((com.nulldev.util.languages.json.v2.JsonObject) o).getDouble(item);
		} else if (o instanceof LazyObject) {
			return (float) ((LazyObject) o).getDouble(item);
		} else {
			throw new UnsupportedOperationException("getFloat(String) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public float getFloat(final String item, final float fallback) {
		nullCheck();
		try {
			if (!this.hasValue(item)) {
				return fallback;
			}
			return this.getFloat(item);
		} catch (Exception e) {
			return fallback;
		}
	}

	public float asFloat() {
		nullCheck();
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			return Float.parseFloat(((com.nulldev.util.languages.json.v2.JsonObject) o).toString());
		} else if (o instanceof Double) {
			return (float) ((double) this.o);
		} else if (o instanceof Float) {
			return (float) this.o;
		} else if (o instanceof Long) {
			return (float) (long) this.o;
		} else if (o instanceof Integer) {
			return (float) (int) this.o;
		} else {
			throw new UnsupportedOperationException("asFloat() -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public String string() {
		nullCheck();
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			return ((com.nulldev.util.languages.json.v2.JsonObject) o).toString();
		} else if (o instanceof String) {
			return (String) o;
		} else if (o instanceof LazyObject) {
			return ((LazyObject) o).toString();
		} else {
			throw new UnsupportedOperationException("string() -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public void debug_array_print() {
		arrayCheck();
		JVM.println("reported length: " + this.length());
		final variable<Long> _i = new variable<Long>(0L);
		try {
			this.asArray().forEach((e) -> {
				_i.set(_i.object() + 1);
			});
		} catch (Exception e) {
		}
		JVM.println("iterated length: " + _i);
	}

	public void print_providers() {
		if (api.equals(JSONAPI.V2)) {
			JVM.println("Arrays: com.nulldev.util.languages.json.v2.JsonArray (v2)");
			JVM.println("Objects: com.nulldev.util.languages.json.v2.JsonObject (v2)");
		} else {
			JVM.println("Arrays: com.nulldev.util.languages.json.fast.LazyArray (fast)");
			JVM.println("Objects: com.nulldev.util.languages.json.fast.LazyObject (fast)");
		}
	}

	public Stream<Object> stream() {
		return stream(false);
	}

	public Stream<Object> parallelStream() {
		return stream(true);
	}

	public Stream<Object> stream(final boolean parallel) {
		nullCheck();
		// TODO: We should work on objects too?
		if (!this.isArray())
			throw new UnsupportedOperationException("o is not an array!");
		if (o instanceof com.nulldev.util.languages.json.v2.JsonArray) {
			return new JsonV2Stream(this, this.o, parallel);
		} else if (o instanceof LazyArray) {
			return new FastJSONStream(this, this.o, parallel);
		} else {
			throw new UnsupportedOperationException("stream -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public Iterator<UniJsonValue> raw_array() {
		nullCheck();
		if (!this.isArray())
			throw new UnsupportedOperationException("o is not an array!");
		if (o instanceof com.nulldev.util.languages.json.v2.JsonArray) {
			return new Iterator<UniJsonValue>() {

				final Iterator<Object> v = ((com.nulldev.util.languages.json.v2.JsonArray) o).iterator();

				@Override
				public UniJsonValue next() {
					final Object jv = v.next();
					if (jv == null)
						throw new NoSuchElementException();
					return new UniJsonValue(jv);
				}

				@Override
				public void remove() {
					v.remove();
				}

				@Override
				public boolean hasNext() {
					return v.hasNext();
				}
			};
		} else if (o instanceof LazyArray) {
			final LazyArray e = ((LazyArray) o);
			return new Iterator<UniJsonValue>() {

				final AtomicInteger currentPos = new AtomicInteger();

				@Override
				public UniJsonValue next() {
					if (!hasNext())
						throw new NoSuchElementException();
					return new UniJsonValue(e.get(currentPos.getAndIncrement()));
				}

				@Override
				public void remove() {
					e.remove(currentPos.get());
					if (currentPos.get() - 1 >= 0) {
						currentPos.getAndDecrement();
					}
				}

				@Override
				public boolean hasNext() {
					return currentPos.get() < e.length();
				}
			};
		} else {
			throw new UnsupportedOperationException("raw_array -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public Object type(final String string) {
		nullCheck();
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			return ((com.nulldev.util.languages.json.v2.JsonObject) o).getType(string);
		} else {
			return null;
		}
	}

	private void arrayCheck() {
		if (!(this.o instanceof JsonArray || this.o instanceof com.nulldev.util.languages.json.v2.JsonArray || this.o instanceof LazyArray
				|| this.o instanceof List<?>)) {
			throw new UnsupportedOperationException("arrayCheck: o is not an JsonArray!");
		}
	}

	private void objectCheck() {
		if (!(this.o instanceof com.nulldev.util.languages.json.v2.JsonObject || this.o instanceof LazyObject || this.o instanceof Map<?, ?>)) {
			throw new UnsupportedOperationException("objectCheck: o is not an JsonObject!");
		}
	}

	public UniJsonValue arrayGetContains(final String string) {
		nullCheck();
		arrayCheck();
		for (final UniJsonValue v : this.asArray()) {
			if (v.hasValue(string)) {
				return v;
			}
		}
		return null;
	}

	public UniJsonValue arrayGetContainsEither(final String... strings) {
		nullCheck();
		arrayCheck();
		for (final String string : strings) {
			for (final UniJsonValue v : this.asArray()) {
				if (v.hasValue(string)) {
					return v;
				}
			}
		}
		return null;
	}

	private static final Splitter MGET_PAT = Splitter.onPattern("\\.");

	public UniJsonValue mget(final String string) {
		nullCheck();
		if (string == null || string.isEmpty())
			throw new NullPointerException("string is invalid!");
		boolean initialSuccess = false;
		UniJsonValue out = null;
		final StringBuilder f = new StringBuilder();
		for (final String s : MGET_PAT.splitToArray(string)) {
			if (out == null) {
				if (!initialSuccess) {
					out = this.get(s);
					initialSuccess = true;
				} else {
					if (f.length() > 0) {
						log.warn("Failed at: " + f.substring(0, f.length() - 1) + ", s: " + s);
						break;
					} else {
						log.warn("Failed at: <root element>" + ", s: " + s);
						break;
					}
				}
			} else {
				if (out == null || out.isNull()) {
					log.warn("Failed at: " + f.substring(0, f.length() - 1) + ", s: " + s);
					break;
				}
				out = out.get(s);
				f.append(s);
				f.append('.');
			}
		}
		return out;
	}

	public List<UniJsonValue> getAllItems() {
		if (this.isNull())
			throw new UnsupportedOperationException("Cannot invoke getAllItems on null!");
		else if (this.isArray()) {
			return ArrayUtils.toArrayList(raw_array());
		} else if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			final List<UniJsonValue> u = FastLists.list(this.length());
			((com.nulldev.util.languages.json.v2.JsonObject) o).forEach((key, object) -> {
				u.add(new UniJsonValue(object));
			});
			return u;
		} else if (o instanceof LazyObject) {
			final List<UniJsonValue> u = FastLists.list(this.length());
			((LazyObject) o).keySet().forEach(key -> {
				u.add(UniJsonValue.this.get(key));
			});
			return u;
		} else
			throw new UnsupportedOperationException("getAllItems -> Unsupported operation on " + o.getClass().getName() + "!");
	}

	public List<UniJsonValue> getAllInListEither(final String... items) {
		if (this.isNull())
			throw new UnsupportedOperationException("Cannot invoke getAllInList on null!");
		if (!this.isArray())
			throw new UnsupportedOperationException("Cannot invoke getAllInList on non-array!");
		final List<UniJsonValue> list = FastLists.list();
		if (items == null || items.length == 0)
			return list;
		this.asArray().forEach((f) -> {
			for (final String item : items)
				if (f.contains(item))
					list.add(f);
		});
		return list;
	}

	public List<UniJsonValue> getAllInList(final String item) {
		if (this.isNull())
			throw new UnsupportedOperationException("Cannot invoke getAllInList on null!");
		if (!this.isArray())
			throw new UnsupportedOperationException("Cannot invoke getAllInList on non-array!");
		final List<UniJsonValue> list = FastLists.list();
		if (item == null || item.isEmpty())
			return list;
		this.asArray().forEach((f) -> {
			if (f.contains(item))
				list.add(f);
		});
		return list;
	}

	@Override
	public void writeTo(final WritableByteChannel _os) throws IOException {
		final OutputStream os = NIOUtils.makeOutputStream(_os);
		this.writeTo(os);
	}

	@Override
	public void writeTo(final OutputStream os) throws IOException {
		this.writeTo(os, false);
	}

	public void writeTo(final OutputStream os, final boolean indent) throws IOException {
		if (os == null)
			throw new NullPointerException("os == null!");
		nullCheck();
		if (o instanceof com.nulldev.util.languages.json.v2.JsonObject && !indent) {
			JsonWriter.on(os).value(this.o).done();
		} else if (o instanceof com.nulldev.util.languages.json.v2.JsonArray && !indent) {
			JsonWriter.on(os).value(this.o).done();
		} else if (o instanceof com.nulldev.util.languages.json.v2.JsonObject && indent) {
			JsonWriter.indent("\t").on(os).value(this.o).done();
		} else if (o instanceof com.nulldev.util.languages.json.v2.JsonArray && indent) {
			JsonWriter.indent("\t").on(os).value(this.o).done();
		} else if (o instanceof LazyObject) {
			final ByteArrayInputStream dataIs = new ByteArrayInputStream(((LazyObject) this.o).toString().getBytes());
			IOUtils.autoCopy(dataIs, os);
			IOUtils.closeAllQuietly(dataIs, os);
		} else if (o instanceof LazyArray) {
			final ByteArrayInputStream dataIs = new ByteArrayInputStream(((LazyArray) this.o).toString().getBytes());
			IOUtils.autoCopy(dataIs, os);
			IOUtils.closeAllQuietly(dataIs, os);
		} else {
			throw new UnsupportedOperationException("writeTo(OutputStream) -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	/**
	 * Returns the items within a JSON object.
	 * 
	 * @returns a Map containing the current items in a object.
	 */
	public Map<String, UniJsonValue> map() {
		if (this.isNull())
			throw new UnsupportedOperationException("Cannot invoke map on null!");
		else if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			final Map<String, UniJsonValue> u = FastMaps.map(((com.nulldev.util.languages.json.v2.JsonObject) o).size());
			((com.nulldev.util.languages.json.v2.JsonObject) o).forEach((key, object) -> {
				u.put(key, new UniJsonValue(object));
			});
			return u;
		} else if (o instanceof LazyObject) {
			final Map<String, UniJsonValue> u = FastMaps.map(((LazyObject) o).length());
			((LazyObject) o).keySet().forEach(key -> {
				u.put(key, UniJsonValue.this.get(key));
			});
			return u;
		} else
			throw new UnsupportedOperationException("map -> Unsupported operation on " + o.getClass().getName() + "!");
	}

	/**
	 * Adds all <T> into the current array.
	 * 
	 * @param values
	 */
	public <T> void addAll(@SuppressWarnings("unchecked") final T... values) {
		if (values == null || values.length == 0)
			return;
		if (!this.isArray())
			return;
		for (final Object obj : values)
			this.add(obj);
	}

	public void populate(final String[][] strings) {
		for (final String[] string : strings) {
			final String key = string[0];
			final String value = ArrayUtils.StringArrayUtils.addFrom(string, 1);
			this.add(key, value);
		}
	}

	public UniJsonValue populate(final Object[][] objects) {
		return populate(objects, true);
	}

	public UniJsonValue populate(final Object[][] objects, final boolean allowNullValues) {
		for (final Object[] object : objects) {
			final String key = (String) object[0];
			final Object value = object[1];
			if (value == null && !allowNullValues)
				continue;
			this.add(key, value);
		}
		return this;
	}

	public String[] getAllNames() {
		assert o != null;
		if (o instanceof LazyObject) {
			final Set<String> s = ((LazyObject) o).keySet();
			return (String[]) s.toArray(new String[s.size()]);
		} else if (o instanceof com.nulldev.util.languages.json.v2.JsonObject) {
			final List<String> f = FastLists.list();
			final Iterator<String> c = ((com.nulldev.util.languages.json.v2.JsonObject) o).keySet().iterator();
			while (c.hasNext()) {
				f.add(c.next());
			}
			return f.toArray(new String[f.size()]);
		} else {
			throw new UnsupportedOperationException("string() -> Unsupported operation on " + o.getClass().getName() + "!");
		}
	}

	public boolean isString() {
		if (this.o == null) {
			return false;
		} else if (this.o instanceof LazyObject) {
			return ((LazyObject) this.o).getType() == LazyType.STRING;
		} else {
			return this.o instanceof String;
		}
	}

	public static boolean probablyJSON(final String message) {
		if (message == null || StringsUtil.isBlank(message)) {
			return false;
		} else if (message.startsWith("[") && message.endsWith("]")) {
			return true;
		} else if (message.startsWith("{") && message.endsWith("}")) {
			return true;
		} else {
			return false;
		}
	}

}
